/**
*  
* @ProjectName credit-framework-common
* @Function ES查询工具类 
* @PackageName com.py.credit.utils.elasticsearch    
* @FileName EsQueryUtil.java    
* @Date 2018年11月5日下午4:16:06
* @Author Min DongXv
* @Copyright (c) 2018, 金电联行 All Rights Reserved.   
*   
*/
package com.py.credit.utils.elasticsearch;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.query.WildcardQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.util.CollectionUtils;

import com.alibaba.fastjson.JSON;
import com.py.framework.core.common.Page;
import com.py.framework.core.log.ExceptionLogger;
import com.py.framework.core.rest.support.RestServiceResult;

/**
 * 
 * @Function ES查询工具类
 * @ClassName EsQueryUtil
 * @Date 2018年10月29日 下午3:10:11
 * @Author Min DongXv
 * 
 */
public class EsQueryUtil {

	// 中文字符判断
	private static final Pattern CHINESE_PATTERN = Pattern.compile(".*[\\u4E00-\\u9FA5\\uF900-\\uFA2D]{1,}.*");

	private EsQueryUtil() {
	}

	/**
	 *
	 * @Function 通过ID获取信息(Map对象)
	 * @FileName EsQueryUtil.java
	 * @MethodName getById
	 * @param index
	 *            索引名称
	 * @param type
	 *            文档type
	 * @param id
	 *            数据ID
	 * @param clazz
	 *            返回数据类型
	 * @return 未查询到返回为null
	 * @Author Min DongXv
	 *
	 */
	public static <T> RestServiceResult<T> getById(String index, String type, String id, Class<T> clazz) {
		RestServiceResult<T> result = new RestServiceResult<>();
		// 获取连接客户端
		RestHighLevelClient client = RestHighLevelClientFactory.getInstance();
		if (null == client) {
			result.setCode(RestServiceResult.CODE_UNKNOW);
			result.setMsg("搜索服务连接失败！");
			return result;
		}
		T value = null;
		try {
			// 获取数据
			GetResponse getResponse = client.get(new GetRequest(index, type, id), RequestOptions.DEFAULT);
			value = JSON.parseObject(getResponse.getSourceAsString(), clazz);
			result.setData(value);
			return result;
		} catch (Exception e) {
			ExceptionLogger.error("EsQueryUtil#getById",
					"ES数据查询异常！index=" + index + ",type=" + type + ",id=" + id + ",ERROR=" + e.getMessage(), e);
			result.setCode(RestServiceResult.CODE_UNKNOW);
			result.setMsg("数据查询异常！");
			return result;
		} finally {
			RestHighLevelClientFactory.closeClinet();
		}
	}

	/**
	 *
	 * @Function 通过ID获取信息(Map返回)
	 * @FileName EsQueryUtil.java
	 * @MethodName getById
	 * @param index
	 *            索引名称
	 * @param type
	 *            文档type
	 * @param id
	 *            数据ID
	 * @param clazz
	 *            返回数据类型
	 * @return 未查询到返回为null
	 * @Author Min DongXv
	 *
	 */
	public static RestServiceResult<Map<String, Object>> getById(String index, String type, String id) {
		RestServiceResult<Map<String, Object>> result = new RestServiceResult<>();
		// 获取连接客户端
		RestHighLevelClient client = RestHighLevelClientFactory.getInstance();
		if (null == client) {
			result.setCode(RestServiceResult.CODE_UNKNOW);
			result.setMsg("搜索服务连接失败！");
			return result;
		}
		Map<String, Object> value = null;
		try {
			// 获取数据
			GetResponse getResponse = client.get(new GetRequest(index, type, id), RequestOptions.DEFAULT);
			value = getResponse.getSourceAsMap();
			result.setData(value);
			return result;
		} catch (Exception e) {
			ExceptionLogger.error("EsQueryUtil#getById",
					"ES数据查询异常！index=" + index + ",type=" + type + ",id=" + id + ",ERROR=" + e.getMessage(), e);
			result.setCode(RestServiceResult.CODE_UNKNOW);
			result.setMsg("数据查询异常！");
			return result;
		} finally {
			RestHighLevelClientFactory.closeClinet();
		}
	}

	/**
	 *
	 * @Function 通过查询条件获取数据列表(实体类返回)
	 * @FileName EsQueryUtil.java
	 * @MethodName getList
	 * @param index
	 *            索引数组，可以单个或多个索引
	 * @param type
	 *            文档type
	 * @param clazz
	 *            返回数据类型
	 * @param page
	 *            分页参数(需要排序的字段:非时间，数值字段排序操作需要开启Fielddata,需要进行映射修改，默认不开启)
	 * @param boolQueryBuilder
	 *            查询条件
	 * @param aggregationBuilder
	 *            聚合条件
	 * @return
	 * @Author Min DongXv
	 *
	 */
	public static <T> RestServiceResult<List<T>> getList(EsPage page, Class<T> clazz, BoolQueryBuilder boolQueryBuilder,
			AggregationBuilder aggregationBuilder, String type, String... index) {
		RestServiceResult<List<T>> result = new RestServiceResult<>();
		// 返回数据集合 默认返回分页数据大小
		List<T> value = new ArrayList<>(page.getLimit());
		// 构建查询资源
		SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
		// 分页开始计算
		sourceBuilder.from((page.getPage() - 1) * page.getLimit());
		// 分页条数
		sourceBuilder.size(page.getLimit());
		// 有排序字段且有指定排序 添加排序
		if (!CollectionUtils.isEmpty(page.getSorts())) {
			for (EsPageSortEntity sort : page.getSorts()) {
				if (null != sort && StringUtils.isNotBlank(sort.getFieldName())) {
					FieldSortBuilder fsb = SortBuilders.fieldSort(sort.getFieldName());
					// 默认为倒序
					if (Page.ASC.equals(sort.getSort())) {
						fsb.order(SortOrder.ASC);
					} else {
						fsb.order(SortOrder.DESC);
					}
					sourceBuilder.sort(fsb);
				}
			}
		}
		if (null != boolQueryBuilder) {
			// 数据查询条件
			sourceBuilder.query(boolQueryBuilder);
		}
		if (null != aggregationBuilder) {
			// 数据聚合条件
			sourceBuilder.aggregation(aggregationBuilder);
		}
		// 查询请求
		SearchRequest searchRequest = new SearchRequest(index);
		// 设置文档
		searchRequest.types(type);
		// 设置查询条件资源
		searchRequest.source(sourceBuilder);
		SearchResponse response = null;
		// 获取es连接
		RestHighLevelClient client = RestHighLevelClientFactory.getInstance();
		if (null == client) {
			result.setCode(RestServiceResult.CODE_UNKNOW);
			result.setMsg("搜索服务连接失败！");
			return result;
		}
		try {
			// 使用默认配置查询获取返回
			response = client.search(searchRequest, RequestOptions.DEFAULT);
			// 获取列表资源
			SearchHits hits = response.getHits();
			// 设置匹配到的数据的总条数
			int totalCount = (int) hits.getTotalHits();
			// ES搜索默认返回最大10000条数据，修改分页信息(大小限制可以使用put请求修改
			// http://127.0.0.1:9200/jdzc_*/_settings content中添加{"index":
			// {"max_result_window": 50000}})
			totalCount = totalCount > 10000 ? 10000 : totalCount;
			for (SearchHit searchHit : hits) {
				T searchValue = JSON.parseObject(searchHit.getSourceAsString(), clazz);
				if (null != searchValue) {
					value.add(searchValue);
				}
			}
			result.setData(value);
			result.setCount(totalCount);
			return result;
		} catch (Exception e) {
			ExceptionLogger.error("EsQueryUtil#getList(对象返回)", "ES数据查询异常！ERROR=" + e.getMessage(), e);
			result.setCode(RestServiceResult.CODE_UNKNOW);
			result.setMsg("查询数据异常！");
			return result;
		} finally {
			RestHighLevelClientFactory.closeClinet();
		}
	}

	/**
	 *
	 * @Function 构造查询条件
	 * @FileName EsQueryUtil.java
	 * @MethodName whereBuilder
	 * @param list
	 *            查询条件集合列表(相当于sql中的where条件)
	 * @return
	 * @Author Min DongXv
	 *
	 */
	public static BoolQueryBuilder whereBuilder(List<EsQueryEntity> list) {
		BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
		if (!CollectionUtils.isEmpty(list)) {
			for (EsQueryEntity e : list) {
				if (null != e) {
					QueryBuilder queryBuilder = null;
					String value = e.getValue();
					// 没有值不进行查询构造
					if (StringUtils.isBlank(value)) {
						continue;
					}
					// 不包含中文的搜索
					if (!CHINESE_PATTERN.matcher(e.getValue()).matches()) {
						queryBuilder = noChineseBuilder(e, value);
					} else {
						queryBuilder = chineseBuilder(e);
					}
					// 查询条件连接
					whereConnect(boolQueryBuilder, queryBuilder, e.getQueryConnector());
				}
			}
		}
		return boolQueryBuilder;
	}

	/**
	 *
	 * @Function 查询条件连接
	 * @FileName EsQueryUtil.java
	 * @MethodName whereConnect
	 * @return
	 * @Author Min DongXv
	 *
	 */
	private static void whereConnect(BoolQueryBuilder boolQueryBuilder, QueryBuilder queryBuilder,
			String queryConnector) {
		if (EsConstants.MUST.equals(queryConnector)) {
			// and操作
			boolQueryBuilder.must(queryBuilder);
		} else if (EsConstants.MUSTNOT.equals(queryConnector)) {
			// not exist操作
			boolQueryBuilder.mustNot(queryBuilder);
		} else if (EsConstants.SHOULD.equals(queryConnector)) {
			// or操作
			boolQueryBuilder.should(queryBuilder);
		}
	}

	/**
	 *
	 * @Function 不含中文条件构造
	 * @FileName EsQueryUtil.java
	 * @MethodName noChineseBuilder
	 * @return
	 * @Author Min DongXv
	 *
	 */
	private static QueryBuilder noChineseBuilder(EsQueryEntity e, String value) {
		QueryBuilder queryBuilder = null;
		// 模糊查询
		if (e.isFuzzy()) {
			// (可能和分词有关)ES
			// wildcardQuery发现只有小写的英文查询才会生效，把字符中的英文转为小写(若有兴趣去研究可以改进该方法)
			value = value.toLowerCase();
			if (e.isFieldMulit()) {
				// 多字段匹配
				queryBuilder = QueryBuilders.boolQuery();
				for (String field : e.getFields()) {
					WildcardQueryBuilder wqb = QueryBuilders.wildcardQuery(field, "*" + value + "*");
					((BoolQueryBuilder) queryBuilder).should(wqb);
				}
			} else {
				// 单字段匹配
				queryBuilder = QueryBuilders.wildcardQuery(e.getField(), "*" + value + "*");
			}
		} else {
			// 精确查询
			if (e.isValueMulit()) {
				// 多值匹配
				queryBuilder = QueryBuilders.termsQuery(e.getField(), StringUtils.split(e.getValue(), ","));
			} else {
				// 单值匹配
				queryBuilder = QueryBuilders.termQuery(e.getField(), e.getValue());
			}
		}
		return queryBuilder;
	}

	/**
	 *
	 * @Function 包含中文条件构造
	 * @FileName EsQueryUtil.java
	 * @MethodName chineseBuilder
	 * @return
	 * @Author Min DongXv
	 *
	 */
	private static QueryBuilder chineseBuilder(EsQueryEntity e) {
		QueryBuilder queryBuilder = null;
		// 是否模糊查询
		if (e.isFuzzy()) {
			if (e.isFieldMulit()) {
				// 多字段匹配 默认使用IK_MAX_WORD分词
				queryBuilder = QueryBuilders.multiMatchQuery(e.getValue(), e.getFields())
						.analyzer(EsConstants.ANALYZER_IK_MAX_WORD);
			} else {
				// 单字段匹配 默认使用IK_MAX_WORD分词
				queryBuilder = QueryBuilders.matchQuery(e.getField(), e.getValue())
						.analyzer(EsConstants.ANALYZER_IK_MAX_WORD);
			}
		} else {
			// 精确搜索
			if (e.isValueMulit()) {
				// 多值匹配
				queryBuilder = QueryBuilders.termsQuery(e.getField(), StringUtils.split(e.getValue(), ","));
			} else {
				// 单值匹配
				queryBuilder = QueryBuilders.termQuery(e.getField(), e.getValue());
			}
		}
		return queryBuilder;
	}

	/**
	 *
	 * @Function 构造查询条件
	 * @FileName EsQueryUtil.java
	 * @MethodName rangeBuilder
	 * @param boolQueryBuilder
	 *            boolQueryBuilder(可以为null)
	 * @param filed
	 *            匹配字段
	 * @param from
	 *            开始
	 * @param to
	 *            结束
	 * @param queryConnector
	 *            查询连接符
	 * @return
	 * @Author Min DongXv
	 *
	 */
	public static BoolQueryBuilder rangeBuilder(BoolQueryBuilder boolQueryBuilder, String filed, Object from, Object to,
			String queryConnector) {
		if (null == boolQueryBuilder) {
			boolQueryBuilder = QueryBuilders.boolQuery();
		}
		RangeQueryBuilder queryBuilder = QueryBuilders.rangeQuery(filed);
		if (null != from) {
			queryBuilder = queryBuilder.from(from);
		}
		if (null != to) {
			queryBuilder = queryBuilder.to(to);
		}
		if (EsConstants.MUST.equals(queryConnector)) {
			// and操作
			boolQueryBuilder.must(queryBuilder);
		} else if (EsConstants.MUSTNOT.equals(queryConnector)) {
			// not exist操作
			boolQueryBuilder.mustNot(queryBuilder);
		} else if (EsConstants.SHOULD.equals(queryConnector)) {
			// or操作
			boolQueryBuilder.should(queryBuilder);
		}
		return boolQueryBuilder;
	}

	/**
	 *
	 * @Function 构造查询条件
	 * @FileName EsQueryUtil.java
	 * @MethodName aggregationBuild
	 * @param list
	 *            聚合参数
	 * @return
	 * @Author Min DongXv
	 *
	 */
	public static AggregationBuilder aggregationBuild(List<EsAggregationEntity> list) {
		AggregationBuilder result = null;
		List<AggregationBuilder> aggList = new ArrayList<>(list.size());
		for (EsAggregationEntity agg : list) {
			if (null != agg) {
				AggregationBuilder aggregationBuilder = null;
				switch (agg.getAggregateOperation()) {
				// 1聚合-计数
				case 1:
					aggregationBuilder = AggregationBuilders.count(agg.getAlias()).field(agg.getField());
					aggList.add(aggregationBuilder);
					break;
				// 2聚合-平均值
				case 2:
					aggregationBuilder = AggregationBuilders.avg(agg.getAlias()).field(agg.getField());
					aggList.add(aggregationBuilder);
					break;
				// 3聚合-最大值
				case 3:
					aggregationBuilder = AggregationBuilders.max(agg.getAlias()).field(agg.getField());
					aggList.add(aggregationBuilder);
					break;
				// 4聚合-最小值
				case 4:
					aggregationBuilder = AggregationBuilders.min(agg.getAlias()).field(agg.getField());
					aggList.add(aggregationBuilder);
					break;
				// 5聚合-加权平均值
				case 5:
					aggregationBuilder = AggregationBuilders.weightedAvg(agg.getAlias());
					aggList.add(aggregationBuilder);
					break;
				// 6聚合-求和
				case 6:
					aggregationBuilder = AggregationBuilders.sum(agg.getAlias()).field(agg.getField());
					aggList.add(aggregationBuilder);
					break;
				default:
					break;
				}
			}
		}
		if (!CollectionUtils.isEmpty(aggList)) {
			for (AggregationBuilder b : aggList) {
				if (null != result) {
					result = result.subAggregation(b);
				} else {
					result = b;
				}
			}
		}
		return result;
	}

	/**
	 *
	 * @Function 通过查询条件获取数据列表(Map返回,Map中会返回ES中带的@timestamp和@version字段)
	 * @FileName EsQueryUtil.java
	 * @MethodName getList
	 * @param index
	 *            索引数组，可以单个或多个索引
	 * @param type
	 *            文档type
	 * @param page
	 *            分页参数(需要排序的字段:非时间，数值字段排序操作需要开启Fielddata,需要进行映射修改，默认不开启)
	 * @param boolQueryBuilder
	 *            查询条件
	 * @param aggregationBuilder
	 *            聚合条件
	 * @return
	 * @Author Min DongXv
	 *
	 */
	public static RestServiceResult<List<Map<String, Object>>> getList(EsPage page, BoolQueryBuilder boolQueryBuilder,
			AggregationBuilder aggregationBuilder, String type, String... index) {
		RestServiceResult<List<Map<String, Object>>> result = new RestServiceResult<>();
		// 返回数据集合 默认返回分页数据大小
		List<Map<String, Object>> value = new ArrayList<>(page.getLimit());
		// 构建查询资源
		SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
		// 分页开始计算
		sourceBuilder.from((page.getPage() - 1) * page.getLimit());
		// 分页条数
		sourceBuilder.size(page.getLimit());
		// 有排序字段且有指定排序 添加排序
		if (!CollectionUtils.isEmpty(page.getSorts())) {
			for (EsPageSortEntity sort : page.getSorts()) {
				if (null != sort && StringUtils.isNotBlank(sort.getFieldName())) {
					FieldSortBuilder fsb = SortBuilders.fieldSort(sort.getFieldName());
					// 默认为倒序
					if (Page.ASC.equals(sort.getSort())) {
						fsb.order(SortOrder.ASC);
					} else {
						fsb.order(SortOrder.DESC);
					}
					sourceBuilder.sort(fsb);
				}
			}
		}
		if (null != boolQueryBuilder) {
			// 数据查询条件
			sourceBuilder.query(boolQueryBuilder);
		}
		if (null != aggregationBuilder) {
			// 数据聚合条件
			sourceBuilder.aggregation(aggregationBuilder);
		}
		// 查询请求
		SearchRequest searchRequest = new SearchRequest(index);
		// 设置文档
		searchRequest.types(type);
		// 设置查询条件资源
		searchRequest.source(sourceBuilder);
		SearchResponse response = null;
		// 获取es连接
		RestHighLevelClient client = RestHighLevelClientFactory.getInstance();
		if (null == client) {
			result.setCode(RestServiceResult.CODE_UNKNOW);
			result.setMsg("搜索服务连接失败！");
			return result;
		}
		try {
			// 使用默认配置查询获取返回
			response = client.search(searchRequest, RequestOptions.DEFAULT);
			// 获取列表资源
			SearchHits hits = response.getHits();
			// 设置匹配到的数据的总条数
			int totalCount = (int) hits.getTotalHits();
			// ES搜索默认返回最大10000条数据，修改分页信息(大小限制可以使用put请求修改
			// http://127.0.0.1:9200/jdzc_*/_settings content中添加{"index":
			// {"max_result_window": 50000}})
			totalCount = totalCount > 10000 ? 10000 : totalCount;
			for (SearchHit searchHit : hits) {
				Map<String, Object> searchValue = searchHit.getSourceAsMap();
				if (null != searchValue) {
					value.add(searchValue);
				}
			}
			result.setData(value);
			result.setCount(totalCount);
			page.setTotalCount(totalCount);
			return result;
		} catch (Exception e) {
			ExceptionLogger.error("EsQueryUtil#getList(Map返回)", "ES数据查询异常！ERROR=" + e.getMessage(), e);
			result.setCode(RestServiceResult.CODE_UNKNOW);
			result.setMsg("查询数据异常！");
			return result;
		} finally {
			RestHighLevelClientFactory.closeClinet();
		}
	}

	// public static void main(String[] args) {
	// EsQueryEntity e = new EsQueryEntity(EsConstants.MUST, new String[] {
	// "A000001", "A000002" }, "91220800771");
	// EsQueryEntity e1 = new EsQueryEntity(EsConstants.MUST, new String[] {
	// "A000001" }, "白城");
	// List<EsQueryEntity> where = new ArrayList<>();
	// where.add(e);
	// where.add(e1);
	// BoolQueryBuilder boolQueryBuilder = EsQueryUtil.whereBuilder(where);
	// EsPage page = new EsPage();
	// List<EsPageSortEntity> sorts = new ArrayList<>();
	// EsPageSortEntity sort = new EsPageSortEntity("updateTime", EsPage.DESC);
	// sorts.add(sort);
	// page.setSorts(sorts);
	// RestServiceResult<List<JSONObject>> list = EsQueryUtil.getList(page,
	// JSONObject.class, boolQueryBuilder, null,
	// "100000", "100000");
	// System.out.println(JSONArray.toJSONString(list));
	// }

}
